#!/bin/bash
# Please excuse the multiple connections.
# Use baclean to clean up the old .pkg.tar.xz files.
# TODO: delegate lock
# TODO: test delegated notification
# TODO: fatal error if pushing package in official repos

# use curses pinentry
unset DISPLAY

base=$(realpath "${BASH_SOURCE[0]%/*}")

# override with -s
site=blackarch.org
# override with -d
sitedir=/var/www/blackarch
# overrise with -r
repo=blackarch
# override with -u
user=$USER
# override with -b
use_existing_sigs=false
# override with -o
handle_official=false

# override with -f
force=false
# override with -w
wait_for_lock=true
# override with -a
always_push=false

# override with --remove
remove=false

we_started_gpg=false
we_started_ssh=false

# default
MKTEMPDIR="/tmp"

error_handler() {
	err 'something bad happened.'
	exit 1
}

err() {
	echo >&2 "ERROR: $*"
}

usage() {
	cat <<EOF
usage: barelease [options and package files]

  -f          Remove /tmp/blackarch.lck before doing anything.
  -w          Do not wait for the lock.
  -a          Always push, even if repo-add fails. This can be dangerous. Please talk
              to paraxor if you do not understand why.
  -k <key>    Specify the gpg key to use for signing.
  -r <repo>   Specify an alternative repo. (Default: blackarch)
  -u <user>   Specify the user to use on the remote host.
  -d <dir>    Specify the location of the repo on the site (Default: /nginx/var/www)
  -s <site>   Specify the site to use (Default: blackarch.org)
  -e          Release all *.pkg.tar.xz files in the cwd.
  -b          Use signature files that are in the same directories as the
              released packages. This disables gpg signing.
  -o          Call scripts/group-official to handle any official packages. This
              implies -b.
  -t <dir>    Specify path for mktemp (Default: /tmp)

  --remove    Remove a package from the repo by name.
EOF
}

cleanup() {
	echo 'cleaning up...'

	unlock

	# Kill gpg-agent.
	$we_started_gpg && kill "$(cut -d: -f2 <<< "$GPG_AGENT_INFO")" 2> /dev/null >&2

	# Kill ssh-agent.
	$we_started_ssh && kill "$SSH_AGENT_PID" 2> /dev/null >&2

	# Clean up temporary directory.
	rm -rf "$tmp"
}

check_args() {
	if (( $# == 0 )) ; then
		usage
		exit 1
	fi
}

parse_args() {
	while (( $# != 0 )) ; do
		case "$1" in
			-f)
				force=true
				;;
			-w)
				wait_for_lock=false
				;;
			-a)
				always_push=true
				;;
			-k)
				key=$2
				shift
				;;
			-r)
				repo=$2
				shift
				;;
			-u)
				user=$2
				shift
				;;
			-s)
				site=$2
				shift
				;;
			-d)
				sitedir=$2
				shift
				;;
			-e)
				packages+=(*.pkg.tar.xz)
				;;
			-b)
				use_existing_sigs=true
				;;
			-o)
				handle_official=true
				use_existing_sigs=true
				;;
      -t)
        MKTEMPDIR=$2
        shift
        ;;
			-h)
				usage
				exit
				;;
			--remove)
				remove=true
				;;
			*)
				packages+=("$1")
				;;
		esac
		shift
	done
}

detect_key() {
	if [[ -z "$key" ]] ; then
		key=$(gpg -K | grep -B1 'BlackArch Developer' | head -n1 | tr -d ' ')
		if [[ -z "$key" ]] ; then
			err 'failed to auto-detect a packager key.'
			exit 1
		fi
	fi
}

check_key() {
	if ! pacman-key -l | grep -q "$key" ; then
		err "$key is not in the keyring."
		exit 1
	fi

	echo "using key: $key"
}

check_packages() {
	$remove && return

	for package in "${packages[@]}" ; do
		if [[ ! -r "$package" ]] ; then
			err "unable to read package file: '$package'."
			exit 1
		fi
	done
}

start_gpg_agent() {
	[[ -z "$GPG_TTY" ]] && export GPG_TTY=$(tty)
	if [[ -z "$GPG_AGENT_INFO" ]] ; then
		echo 'starting gpg-agent...'
		eval $(gpg-agent --daemon --default-cache-ttl 10000)
		we_started_gpg=true
	else
		echo 'gpg-agent already started.'
	fi
	gpg --default-key "$key" -s > /dev/null < /dev/null
}

start_ssh_agent() {
	if [[ -z "$SSH_AGENT_PID" ]] ; then
		echo 'starting ssh-agent...'
		eval $(ssh-agent)
		we_started_ssh=true
	else
		echo 'ssh-agent already started.'
	fi
}

lock() {
	echo "obtaining lock..."
	ssh -l "$user" "$site" "$(declare -f remote_lock); remote_lock $wait_for_lock"
}

# This runs on the remote host.
remote_lock() {
	local wait_for_lock=$1

	if $wait_for_lock ; then
		echo >&2 'waiting for the lock...'
		while ! mkdir -m000 /tmp/blackarch.lck > /dev/null 2>&1 ; do
			sleep 1
		done
	else
		if ! mkdir -m000 /tmp/blackarch.lck > /dev/null 2>&1 ; then
			echo >&2 'unable to obtain lock.'
			exit 1
		fi
	fi
}

unlock() {
	echo "releasing lock..."
	USER=$user "$base/balock" -u
}

# This runs on the remote host.
remote_unlock() {
	rmdir /tmp/blackarch.lck > /dev/null 2>&1 || true
}

update_feed_info() {
	# TODO: finish this.
	#printf '%s\n' "$@" >> "$sitedir/data/updates"
	:
}

do_release() {
	tmp=$(mktemp -d $MKTEMPDIR/barelease.XXXXXXXX)
	mkdir -p "$tmp"/{x86_64,aarch64}

	if ! $remove ; then
		for package in "${packages[@]}" ; do
			if [[ $package == *-any.pkg.tar.xz ]] ; then
				dests=(x86_64 aarch64)
			elif [[ $package == *-x86_64.pkg.tar.xz ]] ; then
				dests=(x86_64)
			elif [[ $package == *-aarch64.pkg.tar.xz ]] ; then
				dests=(aarch64)
			else
				err "unknown arch for '$package'"
				exit 1
			fi

			for dest in "${dests[@]}" ; do
				if $use_existing_sigs ; then
					if ! cp "$package.sig" "$tmp/$dest" 2> /dev/null ; then
						err "could not find $package.sig"
						exit 1
					fi
				fi

				cp "$package" "$tmp/$dest" 2> /dev/null
			done

			notify_list+=("$(sed 's|.*/||;s|.pkg.tar.xz$||' <<< "$package")")
		done
	fi

	if $force ; then
		echo "warning: forcing lock release..."
		unlock
	fi

	lock || exit 1

	for arch in x86_64 aarch64 ; do
		cd "$tmp/$arch"

		if ! $remove && ! ls *.pkg.tar.xz &> /dev/null ; then
			continue
		fi

		echo
		echo "ARCH: $arch"

		echo "pulling..."
		rsync -e "ssh -l $user" "$site:$sitedir/blackarch/$repo/os/$arch"/{blackarch.db.tar.gz,blackarch.files.tar.gz} .

		if [[ ! -r blackarch.db.tar.gz || ! -r blackarch.files.tar.gz ]] ; then
			err 'sync error. try again.'
			exit 1
		fi

		if $remove ; then
			for package in "${packages[@]}" ; do
				echo 'removing from database...'
				repo-remove --nocolor -q blackarch.db.tar.gz "$package" || true

				echo 'removing from files database...'
				repo-remove --nocolor -q blackarch.files.tar.gz "$package" || true
			done
		else
			echo "signing and adding to databases..."
				for package in *.pkg.tar.xz ; do
					if ! $use_existing_sigs ; then
						gpg --no-tty --default-key "$key" --yes -b "$package" > /dev/null 2>&1
					fi
				done

			repo-add --nocolor -n -q blackarch.db.tar.gz *.pkg.tar.xz
		fi

		if $handle_official ; then
			"$base/group-official" blackarch.db.tar.gz
		fi

		gpg --no-tty --default-key "$key" --yes -b blackarch.files.tar.gz > /dev/null 2>&1
		gpg --no-tty --default-key "$key" --yes -b blackarch.db.tar.gz > /dev/null 2>&1

		echo "pushing..."
		if ! $remove ; then
			rsync --ignore-existing --chmod 644 -e "ssh -l $user" ./*.pkg.tar.xz{,.sig} \
			  "$site:$sitedir/blackarch/$repo/os/$arch" || true
		fi
		rsync --chmod 644 -e "ssh -l $user" blackarch.files.tar.gz{,.sig} \
		  blackarch.db.tar.gz{,.sig} "$site:$sitedir/blackarch/$repo/os/$arch" ||
		true
	done
  ssh -l $user $site "date +%s > $sitedir/blackarch/lastupdate"
}

main() {
	trap error_handler ERR

	check_args "$@"
	parse_args "$@"

	check_packages

	detect_key
	check_key

	trap cleanup EXIT

	start_gpg_agent
	start_ssh_agent

	do_release

	if ! $remove ; then
		"$base/banotify" -u "$user" "${notify_list[@]}"
	fi
	update_feed_info "${notify_list[@]}"
}

main "$@"
